package com.ruoyi.common.websocket.server;

import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.Threads;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.common.websocket.handler.WebsocketMsgHandler;
import com.ruoyi.common.websocket.listener.WsOpenListener;

import lombok.extern.slf4j.Slf4j;

@Component
@ServerEndpoint("/websocket/{group}/{username}")//group pc电脑端 app 移动端 gateway 网关 edge 边缘端
@Slf4j
public class WebsocketServer {
	@Value("${server.servlet.context-path}")
	private String contextPath;
	@Value("${server.port}")
	private String port;
	@PostConstruct
	void init(){
		log.info("===================Websocket Server 初始化 ===================");
		ServerEndpoint serverEndpoint = WebsocketServer.class.getAnnotation(ServerEndpoint.class);
		String path = serverEndpoint.value();
		log.info("Websocket Server connect url = ws://127.0.0.1:" + port + contextPath + path);
	}
	@Scheduled(cron = "0/60 * * * * ?")
	public void checkUpdate() throws Exception {
		Iterator<String> it = WebSocketUtils.clients.keySet().iterator();
		while (it.hasNext()) {
			String username = it.next();
			Session session = WebSocketUtils.getSession(username);
			Map<String, Object> userProperties = session.getUserProperties();
			Long updateTime = (Long) userProperties.get("updateTime");
			long now = System.currentTimeMillis();
			if (updateTime != null) {
				long t = (now - updateTime.longValue()) / 1000L;
				if (t > 90) {
					log.warn("websocket client {} 超过 {} 秒心跳包,强制断开链接", username, t);
					session.close();
				}
			}
		}
	}
	

	private Object getFieldInstance(Object obj, String fieldPath) {
		String fields[] = fieldPath.split("#");
		for (String field : fields) {
			obj = getField(obj, obj.getClass(), field);
			if (obj == null) {
				return null;
			}
		}
		return obj;

	}

	private Object getField(Object obj, Class clazz, String fieldName) {
		for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
			try {
				Field field = clazz.getDeclaredField(fieldName);
				field.setAccessible(true);
				return field.get(obj);
			} catch (Exception e) {

			}
		}
		return null;
	}

	@OnOpen
	public void openSession(Session session,@PathParam("group") String group,@PathParam("username") String username) {
		try {
			session.getUserProperties().put("group", group);
			update(session);
			// Map<String, List<String>> params = session.getRequestParameterMap();
			log.info("{} connect to Websocket", username);
			session.setMaxIdleTimeout(30 * 60 * 1000L); // -1永不超时
			// 如果不满足维一性,则关闭重所有连接,让客户端自动重连以满足维一性
			if(WebSocketUtils.clients.containsKey(username)) {
				WebSocketUtils.clients.get(username).close();
				session.close();
				log.warn("{} 存在重复连接",username);
				return;
				//throw new RuntimeException(username + "重复连接");
			}
			WebSocketUtils.clients.put(username, session);
			log.info("Websocket Client size = {}", WebSocketUtils.clients.size());
			String beanKey = group + WsOpenListener.class.getSimpleName();
			if(SpringUtils.containsBean(beanKey)) {	
				WsOpenListener wsOpenListener = SpringUtils.applicationContext.getBean(beanKey, WsOpenListener.class);
				wsOpenListener.onOpen(username, session);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * @param session
	 */
	public void update(Session session) {
		Map<String, Object> userProperties = session.getUserProperties();
		userProperties.put("updateTime", System.currentTimeMillis());
	}
	
	
	
	/**
	 * 
	 * 
	 * onMessage方法是单线程
	 * 
	 * @param username
	 * @param message
	 */
	@OnMessage
	public void onMessage(@PathParam("username") String username, Session session, String message) {
		Threads.execute(() -> {
			try {
				//log.info("Websocket Server recive message -> {}", message);
				JSONObject msg = JSON.parseObject(message);
				String key = WebSocketUtils.getMessageKey(msg);
				Thread thread = (Thread) WebSocketUtils.commands.get(key);
				if (thread != null) {
					synchronized (thread) {
						if (WebSocketUtils.commands.containsKey(key)) {
							WebSocketUtils.commands.put(key, msg);
							thread.notify();
						}
					}
				} else {
					JSONObject commond = msg.getJSONObject("command");
					int iCommand = commond.getInteger("iCommand");
					
					String simpleName = WebsocketMsgHandler.class.getSimpleName();
					String beanKey = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1)  + iCommand;
					if(!SpringUtils.applicationContext.containsBean(beanKey)) {
						beanKey = "defaultMsgHandler";
					}
					WebsocketMsgHandler websocketMsgHandler = SpringUtils.applicationContext.getBean(beanKey, WebsocketMsgHandler.class);
					websocketMsgHandler.invoke(msg);
				}

			} catch (Exception e) {
				e.printStackTrace();
			}
		});
	}

	@OnClose
	public void onClose(@PathParam("username") String username, Session session, CloseReason closeReason) {
		synchronized (session.getId().intern()) {
			if(StringUtils.isBlank(username)) {
				username = WebSocketUtils.getUsername(session);
			}
			if(StringUtils.isNotBlank(username)) {
				// 除非能保证自定义的用户名能保证维一性，否则不能使用自定义的用户名来比较Session
				if(WebSocketUtils.clients.remove(username) != null) {
					log.info("{} disconnect Websocket", username);
					log.info("Websocket Client size = {}", WebSocketUtils.clients.size());
				}
			}
		}
		

	}

	@OnError
	public void onError(Session session, Throwable error) {
		//能监近websocket的异常情况
		error.printStackTrace();	
	}
}